using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using AppToolkit.WPF.UI;

// ReSharper disable CheckNamespace
namespace System.Windows.Controls
// ReSharper restore CheckNamespace
{
    /// <summary>
    /// Can be attached to a <see cref="Control"/> to handle Drag'n'Drop operation.
    /// </summary>
    public class DragSource
    {
        private FrameworkElement _rootFrameworkElement;
        private Point _startPoint;

        /// <summary>
        /// CTOR
        /// </summary>
        /// <param name="ctrl"></param>
        public DragSource(Control ctrl)
        {
            Contract.Requires<ArgumentNullException>(ctrl != null, "ctrl");

            // find root control
            FrameworkElement parent = ctrl;
            while (parent.Parent != null)
                parent = (FrameworkElement)parent.Parent;

            _rootFrameworkElement = parent;

            registerEvents();
        }

        /// <summary>
        /// Returns true, while being in drag mode.
        /// </summary>
        public bool IsDragging { get; private set; }

        /// <summary>
        /// MouseEventArgs from the DD operation, the UIElement selected through
        /// the <see cref="LogicalDragContext"/> filter.
        /// Implementation needs to return the constructed <see cref="DataObject"/>.
        /// </summary>
        public Func<MouseEventArgs, UIElement, DataObject> ProvideDDData { get; set; }

        /// <summary>
        /// Specifies the logical UIElement that defines the context for DD operation
        /// as well as will be highlighted.
        /// </summary>
        public Type LogicalDragContext { get; set; }

        private void registerEvents()
        {
            _rootFrameworkElement.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(OnLeftButtonDown);
            _rootFrameworkElement.PreviewMouseMove += new MouseEventHandler(OnMouseMove);
            _rootFrameworkElement.Unloaded += (p, q) => unregisterEvents();
        }

        private void unregisterEvents()
        {
            _rootFrameworkElement.PreviewMouseLeftButtonDown -= new MouseButtonEventHandler(OnLeftButtonDown);
            _rootFrameworkElement.PreviewMouseMove -= new MouseEventHandler(OnMouseMove);
            _rootFrameworkElement.Unloaded -= (p, q) => unregisterEvents();
            _rootFrameworkElement = null;
        }

        void OnMouseMove(object sender, MouseEventArgs e)
        {
            //TODO: Events kommen nur durch, wenn der drag innerhalb des GridControls vollzogen wird
            // Hypothese: Liegt am GridControl
            if (e.LeftButton == MouseButtonState.Pressed && !IsDragging)
            {
                Point pos = e.GetPosition(null);

                System.Diagnostics.Trace.TraceInformation("{0}={2} - {1}={3}",
                    pos.X, pos.Y, _startPoint.X, _startPoint.Y);

                if (Math.Abs(pos.X - _startPoint.X) > SystemParameters.MinimumHorizontalDragDistance ||
                    Math.Abs(pos.Y - _startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
                {
                    OnStartDrag(e);
                }
            }
        }

        private void OnStartDrag(MouseEventArgs e)
        {
            Contract.Assert(this.ProvideDDData != null, "ProvideDDData is required!");
            System.Diagnostics.Trace.TraceInformation("OnStartDrag");

            UIElement element = determinUIElementFromClickedElement(e.OriginalSource);
            
            if (element == null)
                return;

            this.IsDragging = true;
            Action detach = attachAdorner(element);

            DataObject data = this.ProvideDDData(e,element);
            DragDrop.DoDragDrop(_rootFrameworkElement, data, DragDropEffects.Link);

            this.IsDragging = false;
            detach();
        }

        private UIElement determinUIElementFromClickedElement(object p)
        {
            Contract.Requires<ArgumentNullException>(LogicalDragContext != null);

            DependencyObject dp = p as DependencyObject;
            return (UIElement)VisualTreeHelperExt.GetParentOfType(dp, this.LogicalDragContext);
        }

        private Action attachAdorner(UIElement uIElement)
        {
            //TODO: Je nachdem welches Objekt gedraggt wird, ist der Adorner anders plaziert!
            AdornerLayer alSingle = AdornerLayer.GetAdornerLayer(uIElement);
            RectAdorner rectAdorner = new RectAdorner(uIElement);
            alSingle.Add(rectAdorner);
            
            Action a = () =>
            {
                alSingle.Remove(rectAdorner);
            };
            return a;
        }

        void OnLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _startPoint = e.GetPosition(null);
        }
    }
}
